﻿using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Sino.CapacityCloud.Repositories.Models;
using Sino.CapacityCloud.Services;
using Sino.WebApi.Framework.Core.Filters;
using Sino.WebApi.Framework.Responses;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Sino.CapacityCloud.WebApi.ApiControllers
{

    public class FriendController
        : ApiController<FriendController>
    {

        private readonly IChattingService _chatting;
        private readonly ITmsServiceBus _tmsService;

        public FriendController(
            IHostingEnvironment env,
            IApplicationEnvironment applicationEnvironment,
            IDistributedCache cache,
            ITokenGenertationService tokenGenertationService,
            ITmsServiceBus tmsService,
            ApplicationDbContext dbContext,
            ApplicationUserManager userManager,
            ApplicationRoleManager roleManager,
            ApplicationSignInManager signInManager,
            ILoggerFactory loggerFactory, IChattingService chatting)
            : base(env, applicationEnvironment, cache, tokenGenertationService, dbContext, userManager, roleManager, signInManager, loggerFactory)
        {
            this._chatting = chatting;
            this._tmsService = tmsService;
        }

        public async Task<JsonResult> AddFriend(string userId)
        {
            try
            {
                //this._logger.LogInformation($"Calling [{nameof(AddFriend)}] with [userId = {userId}]...");
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null && user.Id != userId)
                {
                    var friend = await this.UserManager.FindByIdAsync(userId);
                    if (friend != null)
                    {
                        if (this.DataContext.FriendRelationships.Any(x => x.UserId == user.Id && x.FriendId == userId))
                        {
                            throw new ArgumentException($"The user {userId} has been a friend of user {user.Id}.");
                        }
                        var entiy = new FriendRelationship()
                        {
                            UserId = user.Id,
                            FriendId = userId,
                        };
                        this.DataContext.FriendRelationships.Add(entiy);
                        this.DataContext.SaveChanges();
                        var friendHighestRole = await SystemUtils.TryAsync(() =>
                            this.UserManager.GetRoleValueAsync<ApplicationUser, FriendType, FriendType>(friend).GetAwaiter().GetResult(),
                            FriendType.Driver);
                        // FIX: Can we add people other than 'Drivers'?
                        if (friendHighestRole != FriendType.Driver)
                        {
                            throw new ArgumentOutOfRangeException($"The acquiring friend {friend.Id} can't be in role other than {nameof(FriendType.Driver)}.");
                        }
                        var response = new FriendInfoResponse()
                        {
                            Friend = new Friend()
                            {
                                Id = friend.Id,
                                ChatId = friend.Id,
                                Type = friendHighestRole,
                                Profile = friend.Profile,
                                UserName = string.IsNullOrWhiteSpace(friend.RealName) ? friend.PhoneNumber : friend.RealName,
                                HeadUri = friend.GetSmallAvatar(),
                                RatingPoint = this.CalculateRatingPointForUser(friend.Id),
                                CommonLines = this.GetCommonLinesForUser(friend.Id)
                            }
                        };
                        return await this.PackageResultAsync<FriendInfoResponse>(response);
                    }
                    throw new SystemException($"Failed to find the specified friend ApplicationUser object with id = {userId}.");
                }
                throw new SystemException($"Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to add friend because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<FriendInfoResponse>(ex);
            }
        }

        public async Task<JsonResult> DeleteFriend(string userId)
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var friend = await this.UserManager.FindByIdAsync(userId);
                    if (friend != null)
                    {
                        var relationship = this.DataContext.FriendRelationships
                            //.Include(x => x.Friend)
                            //.Include(x => x.User)
                            .FirstOrDefault(x => x.UserId == user.Id && x.FriendId == userId);
                        if (relationship != null)
                        {
                            this.DataContext.FriendRelationships.Remove(relationship);
                            this.DataContext.SaveChanges();
                            return await this.PackageResultAsync<VoidResponse>();
                        }
                    }
                    throw new SystemException($"Failed to find the specified friend ApplicationUser object with id = {userId}.");
                }
                throw new SystemException($"Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to delete the friend for user {userId} because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }

        [SupressUserLogin()]
        public async Task<JsonResult> GetFriendInfo(string userId)
        {
            try
            {
                if (userId == "System")
                {
                    var response = new FriendInfoResponse()
                    {
                        Friend = new Friend()
                        {
                            Id = "System",
                            ChatId = "System",
                            Type = FriendType.System,
                            Profile = "来货拉系统广播",
                            UserName = "系统消息",
                            HeadUri = "https://obxvr4ieq.qnssl.com/SystemAvatar.png",
                            RatingPoint = 0,
                            CommonLines = new Line[0]
                        }
                    };
                    return await this.PackageResultAsync<FriendInfoResponse>(response);
                }
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user == null)
                {
                    var order = this.DataContext.Orders.FirstOrDefault(x => x.ContactChatId == userId);
                    if (order != null)
                    {
                        //user = this.DataContext.Users.FirstOrDefault(x => x.PhoneNumber == order.ContactPhoneNumber);
                        var response = new FriendInfoResponse()
                        {
                            Friend = new Friend()
                            {
                                Id = userId,
                                ChatId = userId,
                                Type = FriendType.Staff,
                                Profile = "",
                                UserName = order.ContactName,
                                HeadUri = "https://obxvr4ieq.qnssl.com/kefu.png",
                                RatingPoint = 0,
                                CommonLines = new Line[0]
                            }
                        };
                        return await this.PackageResultAsync<FriendInfoResponse>(response);
                    }
                    if (user == null)
                    {
                        user = await this.UserManager.FindByPhoneNumberAsync(userId);
                    }
                }
                if (user != null)
                {
                    // TODO: Fix it!
                    var friendHighestRole = await SystemUtils.TryAsync(() =>
                        this.UserManager.GetRoleValueAsync<ApplicationUser, FriendType, FriendType>(user).GetAwaiter().GetResult(),
                        FriendType.Driver);
                    var response = new FriendInfoResponse()
                    {
                        Friend = new Friend()
                        {
                            Id = user.Id,
                            ChatId = user.Id,
                            Type = friendHighestRole,
                            Profile = user.Profile,
                            UserName = string.IsNullOrWhiteSpace(user.RealName) ? user.PhoneNumber : user.RealName,
                            HeadUri = user.GetSmallAvatar(),
                            RatingPoint = this.CalculateRatingPointForUser(user.Id),
                            CommonLines = this.GetCommonLinesForUser(user.Id)
                        }
                    };
                    return await this.PackageResultAsync<FriendInfoResponse>(response);
                }
                throw new SystemException($"Failed to find the specified friend ApplicationUser object with {nameof(userId)}={userId}.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get friend info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<FriendInfoResponse>(ex);
            }
        }

        public async Task<JsonResult> GetFriends()
        {
            try
            {
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var friends = this.DataContext.FriendRelationships
                        .AsNoTracking()
                        .Where(x => x.UserId == user.Id)
                        .Include(x => x.Friend);
                    //.Include(x => x.User)
                    if (friends.Count() > 0)
                    {
                        var response = new FriendListResponse()
                        {
                            Friends = friends.AsEnumerable().Select(x => new Friend()
                            {
                                Id = x.Friend.Id,
                                ChatId = x.Friend.Id,
                                Type = this.UserManager.GetRoleValueAsync<ApplicationUser, FriendType, FriendType>(x.Friend).GetAwaiter().GetResult(),
                                UserName = string.IsNullOrWhiteSpace(x.Friend.RealName) ? x.Friend.PhoneNumber : x.Friend.RealName,
                                Profile = x.Friend.Profile,
                                HeadUri = x.Friend.GetSmallAvatar(),
                                RatingPoint = this.CalculateRatingPointForUser(x.FriendId),
                                CommonLines = this.GetCommonLinesForUser(x.FriendId)
                            }).ToArray()
                        };
                        return await this.PackageResultAsync<FriendListResponse>(response);
                    }
                    return await this.PackageResultAsync<FriendListResponse>();
                }
                throw new SystemException($"Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get your friend list because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<FriendListResponse>(ex);
            }
        }

        public async Task<JsonResult> SearchFriend(string keyword, int startIndex = 0, int count = 10)
        {
            try
            {
                if (startIndex < 0)
                {
                    throw new ArgumentOutOfRangeException($"{nameof(startIndex)}", "startIndex must greater than 0.");
                }
                if (count < 0)
                {
                    throw new ArgumentOutOfRangeException($"{nameof(count)}", "count must greater than 0.");
                }
                var user = await this.UserManager.FindByNameAsync(this.User.Identity.Name);
                if (user != null)
                {
                    var users = this.DataContext.Users
                        .AsNoTracking()
                        .Where(x => x.Id != user.Id && (
                            (!string.IsNullOrWhiteSpace(x.PhoneNumber) && x.PhoneNumber.Contains(keyword)) ||
                            (!string.IsNullOrWhiteSpace(x.UserName) && x.UserName.Contains(keyword)) ||
                            (!string.IsNullOrWhiteSpace(x.RealName) && x.RealName.Contains(keyword))));
                    if (users.Count() > 0)
                    {
                        var response = new FriendListResponse()
                        {
                            Friends = users.Select(x => new Friend()
                            {
                                Id = x.Id,
                                ChatId = x.Id,
                                UserName = string.IsNullOrWhiteSpace(x.RealName) ? x.PhoneNumber : x.RealName,
                                Profile = x.Profile,
                                HeadUri = x.GetSmallAvatar(),
                                //CommonLines = this.GetCommonLinesForUser(x.Id),
                                //Type = SystemUtils.Try(() => this.UserManager.GetRoleValueAsync<ApplicationUser, FriendType, FriendType>(x).GetAwaiter().GetResult(), FriendType.None),
                                //RatingPoint = this.CalculateRatingPointForUser(x.Id)
                            }).Skip(startIndex).Take(count).ToArray(),
                            Total = users.Count()
                        };
                        return await this.PackageResultAsync<FriendListResponse>(response);
                    }
                    return await this.PackageResultAsync<FriendListResponse>();
                }
                throw new SystemException($"Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to search for a friend because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<FriendListResponse>(ex);
            }
        }

        private double CalculateRatingPointForUser(string userId)
        {
            var userRatings = this.DataContext.Ratings.AsNoTracking().Where(x => x.UserId == userId).Select(x => x.TotalRating).ToList();
            if (userRatings.Count > 0)
            {
                return userRatings.Average();
            }
            return 0.0;
        }

        private Line[] GetCommonLinesForUser(string userId)
        {
            return this.DataContext.CommonTransportLinkages
                .AsNoTracking()
                .Where(x => x.UserId == userId)
                .Include(x => x.TransportLinkage)
                .Select(x => new Line()
                {
                    Id = x.CommonTransportLinkageId,
                    StartingPlaceCode = x.TransportLinkage.StartingPlaceCode,
                    StartingPlaceName = x.TransportLinkage.StartingPlaceName,
                    StartingPlaceLatitude = x.TransportLinkage.StartingPlaceLocation.Latitude,
                    StartingPlaceLongitude = x.TransportLinkage.StartingPlaceLocation.Longitude,
                    DestinationName = x.TransportLinkage.DestinationPlaceName,
                    DestinationCode = x.TransportLinkage.DestinationPlaceCode,
                    DestinationLatitude = x.TransportLinkage.DestinationPlaceLocation.Latitude,
                    DestinationLongitude = x.TransportLinkage.DestinationPlaceLocation.Longitude
                }).ToArray();
        }

    }

}